import compose from './compose';
import {
  base,
  retry,
  throttle,
  encrypt,
  metrics,
  logger,
  auth,
  cache,
} from '../middleware';
import {
  HttpRequestConfig,
  HttpResponse,
  Middleware,
  MiddlewareContext,
  MiddlewareFactory,
  ErrorHandler,
  HttpError,
  IHttpClient,
} from './types';
import { deepMerge } from '../utils';
import { Uploader, UploadOptions } from './Uploader';
import { ConfigUtils } from './ConfigUtils';

export default class HttpClient implements IHttpClient {
  private middleware: Middleware[] = [];
  private middlewareFactories = new Map<string, MiddlewareFactory>();
  private errorHandlers: ErrorHandler[] = [];
  private uploader: Uploader;
  private async coreRequest<T = any>(
    config: HttpRequestConfig
  ): Promise<HttpResponse<T>> {
    try {
      const response =
        config.uploadOptions || config.onUploadProgress
          ? await this.handleUploadRequest(config.url as string, config)
          : await this.handleRegularRequest(config.url as string, config);

      return await this.processResponse<T>(response, config);
    } catch (error: any) {
      throw new HttpError(
        error.message,
        config,
        error.code,
        error.request,
        error.response
      );
    }
  }

  private async handleUploadRequest(
    url: string,
    config: HttpRequestConfig
  ): Promise<Response> {
    const uploadData = config.body || config.data;
    // 使用ConfigUtils合并上传配置
    const uploadOptions = ConfigUtils.mergeConfigs(
      {
        timeout: config.timeout,
        headers: config.headers,
        signal: config.signal,
        onUploadProgress: config.onUploadProgress,
      },
      config.uploadOptions || {}
    );
    return this.uploader.upload(url, uploadData, uploadOptions);
  }

  private async handleRegularRequest(
    url: string,
    config: HttpRequestConfig
  ): Promise<Response> {
    const { body, headers } = this.prepareRequestBody(config);
    return fetch(url, {
      method: config.method?.toUpperCase() || 'GET',
      headers,
      body,
    });
  }

  private prepareRequestBody(config: HttpRequestConfig): {
    body: BodyInit | undefined;
    headers: Record<string, string>;
  } {
    let body: BodyInit | undefined;
    const headers = { ...(config.headers || {}) };

    if (config.data) {
      if (config.data instanceof FormData) {
        body = config.data;
        delete headers['Content-Type'];
      } else if (
        config.data instanceof Blob ||
        config.data instanceof ArrayBuffer
      ) {
        body = config.data;
      } else if (typeof config.data === 'string') {
        body = config.data;
      } else {
        body = JSON.stringify(config.data);
        if (!headers['Content-Type']) {
          headers['Content-Type'] = 'application/json';
        }
      }
    }
    return { body, headers };
  }

  private async processResponse<T>(
    response: Response,
    config: HttpRequestConfig
  ): Promise<HttpResponse<T>> {
    const responseType = config.responseType || 'json';
    const data = await this.parseResponseData(response, responseType);
    const headers = this.extractResponseHeaders(response);

    const validateStatus =
      config.validateStatus ||
      ((status: number) => status >= 200 && status < 300);
    if (!validateStatus(response.status)) {
      const httpResponse = this.createHttpResponse(
        data,
        response,
        headers,
        config
      );
      throw new HttpError(
        `Request failed with status code ${response.status}`,
        config,
        response.statusText,
        response.status,
        httpResponse
      );
    }

    return this.createHttpResponse(data, response, headers, config);
  }

  private async parseResponseData(
    response: Response,
    responseType: string
  ): Promise<any> {
    switch (responseType) {
      case 'json':
        return response.json();
      case 'text':
        return response.text();
      case 'blob':
        return response.blob();
      case 'arraybuffer':
        return response.arrayBuffer();
      default:
        return response.json();
    }
  }

  private extractResponseHeaders(response: Response): Record<string, string> {
    const headers: Record<string, string> = {};
    response.headers.forEach((value, key) => {
      headers[key] = value;
    });
    return headers;
  }

  private createHttpResponse<T>(
    data: T,
    response: Response,
    headers: Record<string, string>,
    config: HttpRequestConfig
  ): HttpResponse<T> {
    return {
      data,
      status: response.status,
      statusText: response.statusText,
      headers,
      config,
      request: null,
    };
  }

  private mergeConfig(
    defaultConfig: HttpRequestConfig,
    requestConfig: Partial<HttpRequestConfig>
  ): HttpRequestConfig {
    return deepMerge(defaultConfig, requestConfig) as HttpRequestConfig;
  }

  private callErrorHandlers(ctx: MiddlewareContext): void {
    for (const handler of this.errorHandlers) {
      try {
        handler(ctx.error as HttpError, ctx);
      } catch (err) {
        console.error('Error handler failed:', err);
      }
    }
  }
  static create(config: HttpRequestConfig = {}): HttpClient {
    return new this(config);
  }

  config: HttpRequestConfig;

  constructor(config: HttpRequestConfig = {}) {
    this.config = config;
    // 提取基础配置传递给Uploader
    const uploaderConfig = {
      timeout: config.timeout,
      headers: config.headers,
      signal: config.signal,
      onUploadProgress: config.onUploadProgress,
    };
    this.uploader = new Uploader(uploaderConfig);
    this.register('base', base);
    this.register('retry', retry);
    this.register('throttle', throttle);
    this.register('encrypt', encrypt);
    this.register('metrics', metrics);
    this.register('logger', logger);
    this.register('auth', auth);
    this.register('cache', cache);
    this.use('base', config);
  }

  use(middleware: string | Middleware, options?: any): this {
    if (typeof middleware === 'string') {
      const factory = this.middlewareFactories.get(middleware);
      if (!factory)
        throw new Error(`Middleware "${middleware}" is not registered`);
      middleware = factory(options);
    }
    if (typeof middleware !== 'function')
      throw new TypeError('Middleware must be a function');
    this.middleware.push(middleware);
    return this;
  }

  register<T = any>(name: string, factory: MiddlewareFactory<T>): this {
    if (this.middlewareFactories.has(name)) {
      console.warn(`Middleware factory "${name}" is being overwritten`);
    }
    this.middlewareFactories.set(name, factory);
    return this;
  }

  onError(handler: ErrorHandler): this {
    if (typeof handler !== 'function')
      throw new TypeError('Error handler must be a function');
    this.errorHandlers.push(handler);
    return this;
  }

  private processUrl(config: HttpRequestConfig): string {
    let finalUrl = config.url || '';
    // 如果 url 是绝对路径（如 https://example.com/api），直接使用 url
    if (!/^https?:\/\//i.test(finalUrl)) {
      const baseUrl = config.baseURL
        ? config.baseURL.replace(/\/$/, '')
        : window?.location.origin;
      finalUrl = `${baseUrl}/${config.url?.replace(/^\//, '') || ''}`;
    }
    // 2. 处理查询参数
    if (config.params) {
      const urlObject = new URL(finalUrl);
      for (const [key, value] of Object.entries(config.params)) {
        if (value != null) urlObject.searchParams.set(key, String(value));
      }
      finalUrl = urlObject.toString();
    }

    return finalUrl;
  }

  async request<T = any>(config: HttpRequestConfig): Promise<HttpResponse<T>> {
    const mergedConfig = this.mergeConfig(this.config, config);
    const finalUrl = this.processUrl(mergedConfig);

    // Update config with final URL
    const processedConfig = { ...mergedConfig, url: finalUrl };

    const ctx: MiddlewareContext = {
      config: processedConfig,
      request: null,
      response: null,
      error: null,
      state: {},
    };

    if (this.middleware.length === 0) {
      try {
        ctx.response = await this.coreRequest<T>(processedConfig);
        return ctx.response;
      } catch (error) {
        const httpError = error as HttpError;
        ctx.error = httpError;
        this.callErrorHandlers(ctx);
        throw ctx.error;
      }
    }

    const coreRequestDispatchMiddleware: Middleware = async (context, next) => {
      if (context.response) {
        await next();
        return;
      }
      try {
        context.response = await this.coreRequest<T>(processedConfig);
      } catch (err) {
        context.error = err as HttpError;
      }
      await next();
    };

    const middlewareChain = [...this.middleware, coreRequestDispatchMiddleware];
    const fn = compose(middlewareChain);

    try {
      await fn(ctx);
      if (ctx.error) {
        this.callErrorHandlers(ctx);
        throw ctx.error;
      }
      if (!ctx.response) {
        const unhandledError = new Error(
          'Request pipeline completed without a response or error.'
        ) as HttpError;
        unhandledError.config = ctx.config;
        ctx.error = unhandledError;
        this.callErrorHandlers(ctx);
        throw ctx.error;
      }
      return ctx.response;
    } catch (error) {
      if (!ctx.error || ctx.error !== error) {
        ctx.error = error as HttpError;
        this.callErrorHandlers(ctx);
      }
      throw ctx.error;
    }
  }
  get<T = any>(
    url: string,
    params?: any,
    config?: HttpRequestConfig
  ): Promise<HttpResponse<T>> {
    return this.request({ ...config, method: 'GET', url, params });
  }

  post<T = any>(
    url: string,
    data?: any,
    config?: HttpRequestConfig
  ): Promise<HttpResponse<T>> {
    return this.request({ ...config, method: 'POST', url, data });
  }

  put<T = any>(
    url: string,
    data?: any,
    config?: HttpRequestConfig
  ): Promise<HttpResponse<T>> {
    return this.request({ ...config, method: 'PUT', url, data });
  }

  delete<T = any>(
    url: string,
    config?: HttpRequestConfig
  ): Promise<HttpResponse<T>> {
    return this.request({ ...config, method: 'DELETE', url });
  }

  patch<T = any>(
    url: string,
    data?: any,
    config?: HttpRequestConfig
  ): Promise<HttpResponse<T>> {
    return this.request({ ...config, method: 'PATCH', url, data });
  }

  async upload<T = any>(
    url: string,
    data: FormData | Blob | ArrayBuffer | string,
    options: UploadOptions = {}
  ): Promise<HttpResponse<T>> {
    return this.request<T>({
      method: 'POST',
      url,
      body: data,
      uploadOptions: options,
      ...options,
    });
  }
}
